<!DOCTYPE html>
<!-- Copyright © 2021~2022 by wzh -->
<html lang="zh">
	<head>
		<meta charset="UTF-8" />
		<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport" />
		<title>人机对战 | 2D五子棋</title>
		
		<!--icon-->
		<link href="./img/icon.png" rel="icon" type="image/x-icon"/>
		
		<!-- 异步fonts加载 -->
		<script src="./js/webfontloader.js"></script>
		<script>
		if (typeof require != "undefined"){ //electron
			console.log("electron")
			document.write(`<link href="./css/fonts.css" rel="stylesheet" type="text/css" />`);
			window.onload = function(){
				console.log("onload")
				$("body").addClass("wf-active");
			}
		}else{
			WebFont.load({
				google: {
					families: ["kt", "st", "xs", "zyyt"],
					api: "./css/fonts.css"
				}
			});
		}
		</script>
		
		<style>
		/* 字体缩放方案 */
		/* @media screen and (max-width:320px){
			body {font-size: calc(0.018 * 320px + 6px) !important;}
		}
		body{
			font-size: calc(1.8vw + 6px) !important;
		}
		@media screen and (min-width: 960px){
			body {font-size: calc(0.018 * 960px + 6px) !important;}
		} */
		
		/* element */
		*{
			outline: none; /* 无点击线 */
		}
		html{
			/* 禁止选择 */
			-moz-user-select: none;
			-khtml-user-select: none;
			user-select: none;
			
			background-color: #fffae8; /* 背景 */
		}
		html.outDown{
			min-height: 100%;
			background: #fffae8 url("./img/background.jpg") no-repeat fixed center;
			background-size: cover;
			max-width: 100%;
			margin: auto;
			
			overflow-y: hidden;
		}
		
		body{
			position: absolute;
			width: 100%;
			height: 100%;
			left: 0;
			top: 0;
			margin: 0;
			
			background-color: #fffae8; /* 背景 */
			
			opacity: 0; /* 完全透明 */
			transition: opacity 1s, top 0.6s; /* 出入动画 */
		}
		html.fadeIn > body{
			opacity: 1; /* 不透明 */
		}
		html.outDown > body{
			top: 100%; /* 下滑 */
			overflow-y: hidden;
		}
		
		
		/* header */
		body > header{
			display: flex;
			width: 100%;
			background-color: #aef;
		}
		body > header > i{
			flex: none;
			padding: 0.3rem;
			font-size: 2em !important;
			font-weight: bold;
			
			background-color: #aef;
			border: none;
			transition: background-color 0.3s;
		}
		body > header > i:focus{
			background-color: #afe;
		}
		body > header > i:active{
			background-color: #9ed;
		}
		body > header > h1{
			display: inline-block;
			width: 100%;
			margin: 0;
			padding: 6px 0;
			text-align: center;
			font-weight: bold;
		}
		
		
		/* main */
		#main{
			margin: 8px;
		}
		
		/* 双方 */
		#main > div.side{
			display: flex;
			align-items: center;
		}
		#main > div.opposite{
			text-align: left;
			justify-content: flex-start;
		}
		#main > div.self{
			text-align: right;
			justify-content: flex-end;
		}
		
		#main > div.side > i{ /* 图标 */
			display: inline-block;
			width: 1.6rem;
			height: 1.6rem;
			margin: 0.1rem;
			border-radius: 50%;
		}
		#main > div.black > i{ /* 黑 */
			background-color: black;
			border: 1px solid white;
		}
		#main > div.white > i{ /* 白 */
			background-color: white;
			border: 1px solid black;
		}
		
		#main > div.side > div{
			display: inline-flex;
			flex-direction: column;
		}
		
		#main > div.side > div > p.name{ /* 名称 */
			display: inline-block;
			margin: 0.1rem;
			font-weight: bold; /* 粗体 */
		}
		#main > div.black > div > p.name:before{ /* 黑 */
			content: "黑方";
		}
		#main > div.white > div > p.name:before{ /* 白 */
			content: "白方";
		}
		
		#main > div.side > div > p.time{ /* 时长 */
			display: inline-block;
			margin: 0.1rem;
		}
		
		/* 棋盘 */
		#board{
			display: block;
			width: 0;
			height: 0;
			margin: 0 auto;
			border: 2px outset #fcce73;
			background-color: #fcce73;
		}
		
		/* 工具按钮 */
		#toolBtn > button{
			width: 2.6rem;
			height: 2.6rem;
			margin: 0.1rem;
			font-size: 1em;
			
			background-color: #fc7;
			border: 1px solid #c94;
			border-radius: 50%;
			transition: background-color 0.3s;
			box-shadow: 1px 1px 2px #888,
				3px 3px 2px #888;
		}
		#toolBtn > button:active{
			background-color: #eb6;
			box-shadow: 3px 3px 3px #888;
		}
		
		/* 工具 */
		#tools > .scores{
			border: 1px solid #ddd;
		}
		
		#tools > .review > *{
			display: inline-block;
			width: 2rem;
			height: 2rem;
			margin: 0.2rem;
			padding: 0.3rem;
			background-color: #ddd;
			border: 3px outset #ddd;
			box-shadow: 1px 1px 2px #888,
				3px 3px 2px #888;
		}
		#tools > .review > *:active{
			background-color: #ccc;
			border: 3px inset #ddd;
		}
		</style>
		
		<!-- jquery -->
		<script src="./js/jquery.min.js"></script>
		<script>
		if (typeof require != "undefined") //electron
			window.$ = window.jQuery = require("./js/jquery.min.js");
		</script>
		
		<!-- 语言包 -->
		<script src="./lang/lang.js" defer></script>
		
		<!-- layui -->
		<!--<link rel="stylesheet" href="https://unpkg.com/layui@2.6.4/dist/css/layui.css">
		<script src="https://unpkg.com/layui@2.6.8/dist/layui.js"></script>-->
		<script src="./layui/layui.js"></script>
		<link rel="stylesheet" href="./layui/css/layui.css" />
		
		<!-- 调试工具 -->
		<!-- <script src="https://cdn.jsdelivr.net/npm/eruda"></script>
		<script src="https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js"></script>
		<script>
		if ( /ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile/.test( navigator.userAgent.toLowerCase() ) ){
			//手机
			eruda.init();
			new VConsole();
		}
		</script> -->
		
		<script src="./js/js_plus.min.js"></script> <!-- js+ -->
		
		<!-- PianoMusic Bgm -->
		<script src="./js/acoustic_grand_piano-ogg.js"></script>
		<script src="./js/music.js"></script>
		<script src="./js/bgm.js"></script>
		
		<script src="https://wzh656.github.io/gobang/js/dynamic.js" async></script>
		<script src="https://wzh656.github.io/gobang/js/count.js" async></script>
		<script>
		var _hmt = _hmt || [];
		(function() {
			var hm = document.createElement("script");
			hm.src = "https://hm.baidu.com/hm.js?c6b8f0ad7ec671e54d83e86a67280999";
			var s = document.getElementsByTagName("script")[0]; 
			s.parentNode.insertBefore(hm, s);
		})();
		</script>
		
		<!-- Global site tag (gtag.js) - Google Analytics -->
		<script async src="https://www.googletagmanager.com/gtag/js?id=G-WTWJEHK1C2"></script>
		<script>
		window.dataLayer = window.dataLayer || [];
		function gtag(){dataLayer.push(arguments);}
		gtag('js', new Date());
		
		gtag('config', 'G-WTWJEHK1C2');
		</script>
		
	</head>
	<body>
		<header id="header">
			<i id="back" class="layui-icon layui-icon-return"></i>
			<h1>人机对战 2D</h1>
			<i id="restart" class="layui-icon layui-icon-refresh"></i>
		</header>
		
		<section id="main">
			<div class="side opposite">
				<i></i>
				<div>
					<p class="name"></p>
					<p class="time">
						<span>局时：</span><span class="total">05:00</span> &nbsp;
						<span>步时：</span><span class="step">30s</span>
					</p>
				</div>
			</div>
			<canvas id="board" width="600" height="600"></canvas>
			<div class="side self">
				<div>
					<p class="name"></p>
					<p class="time">
						<span>局时：</span><span class="total">05:00</span> &nbsp;
						<span>步时：</span><span class="step">30s</span>
					</p>
				</div>
				<i></i>
			</div>
			<div id="toolBtn">
				<button class="scores">棋局<br>评分</button>
				<button class="analyse">棋局<br>分析</button>
				<button class="review" disabled>复盘<br>回放</button>
			</div>
			<div id="tools">
				<canvas class="scores" height="36" style="display: none;"></canvas>
				<div class="review" style="display: none;">
					<svg class="last" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" xmlns:xlink="http://www.w3.org/1999/xlink">
						<polygon points="94.65,10 25.35,50 94.65,90 94.65,10" />
						<polygon points="5.35,10 25.35,10 25.35,90 5.35,90" />
					</svg>
					<svg class="play" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" xmlns:xlink="http://www.w3.org/1999/xlink">
						<polygon points="6.7,0 93.3,50 6.7,100 6.7,0" />
					</svg>
					<svg class="stop" style="display: none;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" xmlns:xlink="http://www.w3.org/1999/xlink">
						<polygon points="20,0 40,0 40,100 20,100" />
						<polygon points="60,0 80,0 80,100 60,100" />
					</svg>
					<svg class="next" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" xmlns:xlink="http://www.w3.org/1999/xlink">
						<polygon points="5.35,10 74.65,50 5.35,90 5.35,10" />
						<polygon points="94.65,10 74.65,10 74.65,90 94.75,90" />
					</svg>
				</div>
			</div>
		</section>
		
<script>
let WIDTH;
function resize(){
	console.log("resize")
	WIDTH = Math.min(innerWidth-20, innerHeight - $("#main > .side").height()*2);
	$("#board").css("width", WIDTH + "px")
		.css("height", WIDTH + "px")
		.attr("width", 2*WIDTH)
		.attr("height", 2*WIDTH);
	
	$("#tools > .scores").attr("width", innerWidth-18);
}
resize();
//$(window).resize(resize);

document.addEventListener("plusready", function(){
	plus.navigator.setStatusBarBackground("#aef");
	plus.navigator.setStatusBarStyle("dark");
	plus.key.addEventListener("backbutton", function(){
		go("index.html");
	});
}, false);
</script>
<script>
$("html").addClass("fadeIn");
function go(url){
	$("html").addClass("outDown");
	setTimeout(function(){
		if (typeof plus != "undefined"){
			plus.navigator.setStatusBarBackground("#000");
			plus.navigator.setStatusBarStyle("light");
		}
		location.href = url;
	}, 600);
	layer.load(0);
}


$("button, input[type='button'], svg, header > i").click(function(){
	playEffect(); //播放音效
});



class Board{
	constructor ({canvas, background, rows, columns, picSize}){
		this.canvas = canvas;
		this.width = canvas.width;
		this.height = canvas.height;
		this.ctx = canvas.getContext("2d");
		this.background = background;
		this.rows = rows;
		this.columns = columns;
		this.pieces = []; //棋子
		this.picSize = picSize; //棋子大小
		this.spacingX = (this.width - picSize) / (columns-1); //x横向间隔
		this.spacingY = (this.height - picSize) / (rows-1); //y纵向间隔
		this.updateCallback = { //更新后回调
			win: null,
			scores: null
		};
	}
	
	//棋盘坐标(理想) 转 横纵坐标(理想)
	ij2xy(i, j, place){
		return [
			this.picSize/2 + i * this.spacingX + this.spacingX/2*place,
			this.picSize/2 + j * this.spacingY + this.spacingY/2*place
		];
	}
	//横纵坐标(真实) 转 棋盘坐标(理想)
	xy2ij(x, y, place){
		const scaleX = this.width / $(this.canvas).width(), //理想:真实
			scaleY = this.height / $(this.canvas).height(); //理想:真实
		return [
			Math.round( (x*scaleX - this.picSize/2 - this.spacingX/2*place) / this.spacingX ),
			Math.round( (y*scaleY - this.picSize/2 - this.spacingY/2*place) / this.spacingY )
		];
	}
	
	
	//添加棋子
	add(i, j, place=0, color, borderColor, borderWidth=1, picSize=this.picSize){
		const pic = {
			i, j,
			color,
			border: {
				color: borderColor, //边框颜色
				width: borderWidth //边框粗细
			},
			place, //格内:1, 格点:0
			picSize
		};
		this.pieces.push(pic);
		
		return this.update(); //重绘
	}
	
	//删除棋子 [i~j]
	remove(i, j=i+1){
		this.pieces.splice(i, j-i);
		
		return this.update(); //重绘
	}
	
	//清除棋子
	clear(){
		this.pieces.splice(0, this.pieces.length);
		
		return this.update(); //重绘
	}
	
	
	//画一个棋子
	drawPiece(pic){
		const ctx = this.ctx;
		
		ctx.beginPath();
		const [x, y] = this.ij2xy(pic.i, pic.j, pic.place);
		ctx.arc(x, y, pic.picSize/2, 0, Math.PI*2);
		if (pic.color){ //背景
			ctx.fillStyle = pic.color;
			ctx.fill();
		}
		if (pic.border.color){ //边框
			ctx.strokeStyle = pic.border.color;
			ctx.lineWidth = pic.border.width;
			ctx.stroke();
		}
	}
	
	//更新棋盘
	update(){
		const ctx = this.ctx;
		
		//绘制背景
		ctx.beginPath();
		ctx.fillStyle = this.background;
		ctx.fillRect(0, 0, this.width, this.height);
		
		//绘制网格
		ctx.beginPath();
		ctx.strokeStyle = "#000";
		ctx.lineWidth = 1;
		for (let x=0; x<this.columns; x++){
			ctx.moveTo(...this.ij2xy(x, 0, 0));
			ctx.lineTo(...this.ij2xy(x, this.rows-1, 0));
			ctx.stroke();
		}
		for (let y=0; y<this.rows; y++){
			ctx.moveTo(...this.ij2xy(0, y, 0));
			ctx.lineTo(...this.ij2xy(this.columns-1, y, 0));
			ctx.stroke();
		}
		
		//绘制棋子
		for (const v of this.pieces)
			this.drawPiece(v);
		
		//回调
		if (this.updateCallback.analyse)
			this.updateCallback.analyse();		
		if (this.updateCallback.win)
			this.updateCallback.win();
		
		return this;
	}
}


const {N=5, cols=19, rows=19, place=0, totalTime=300, stepTime=30} = JSON.parse(localStorage.getItem("五子棋_游戏设置") || "{}"),
	{mistake=true} = JSON.parse(localStorage.getItem("五子棋_个性化设置") || "{}");
$("#board").css("height", WIDTH *rows/cols + "px")
	.attr("height", 2*WIDTH *rows/cols);
const board = new Board({ //棋盘
	canvas: $("#board")[0],
	background: "#fcce73",
	rows: rows + place,
	columns: cols + place,
	picSize: $("#board").attr("width")/(cols + place) *0.9
}).add((cols-1)/2, (rows-1)/2, place, "#000", undefined, undefined, 10) //多加一个以备删除
	.add((cols-1)/2, (rows-1)/2, place, "#000", undefined, undefined, 10);

let gameOverMusic; //游戏结束音乐
const game_record = JSON.parse(localStorage.getItem("五子棋_战绩") || "{}"),
	sizeText = `${cols}*${rows}`;

game_record.two = game_record.two || {}; //2D
game_record.two.win = game_record.two.win || 0;
game_record.two.fail = game_record.two.fail || 0;
game_record.two.total = game_record.two.total || 0;

game_record.two[N] = game_record.two[N] || {};
game_record.two[N].win = game_record.two[N].win || 0;
game_record.two[N].fail = game_record.two[N].fail || 0;
game_record.two[N].total = game_record.two[N].total || 0;

game_record.two[N][0] = game_record.two[N][0] || {};
game_record.two[N][0].win = game_record.two[N][0].win || 0;
game_record.two[N][0].fail = game_record.two[N][0].fail || 0;
game_record.two[N][0].total = game_record.two[N][0].total || 0;

game_record.two[N][1] = game_record.two[N][1] || {};
game_record.two[N][1].win = game_record.two[N][1].win || 0;
game_record.two[N][1].fail = game_record.two[N][1].fail || 0;
game_record.two[N][1].total = game_record.two[N][1].total || 0;

game_record.two[N][0][sizeText] = game_record.two[N][0][sizeText] || {};
game_record.two[N][0][sizeText].win = game_record.two[N][0][sizeText].win || 0;
game_record.two[N][0][sizeText].fail = game_record.two[N][0][sizeText].fail || 0;
game_record.two[N][0][sizeText].total = game_record.two[N][0][sizeText].total || 0;

game_record.two[N][1][sizeText] = game_record.two[N][1][sizeText] || {};
game_record.two[N][1][sizeText].win = game_record.two[N][1][sizeText].win || 0;
game_record.two[N][1][sizeText].fail = game_record.two[N][1][sizeText].fail || 0;
game_record.two[N][1][sizeText].total = game_record.two[N][1][sizeText].total || 0;

function saveGameRecord(){
	localStorage.setItem("五子棋_战绩", JSON.stringify(game_record));
}



let game = {
		first: null, //是否先行
		turn: 1, //下棋方 1:黑, 0:白
		startTime: null, //开始时间
		steps: 0, //下棋总步数
		winner: false, //获胜者 1:黑, 0:白, null:平 false:未获胜
		t0: [null, null], //上次时间
		ids: [null, null], //计时器id
		time: [
			{total: totalTime, step: stepTime}, //0: 白
			{total: totalTime, step: stepTime} //1: 黑
		], //剩余时间
		timeWarning: { //时间警告
			total: {
				60: ["#f00", 1.5],
				180: ["#fa0", 1.2],
				Infinity: ["#000", 1]
			},
			step: {
				10: ["#f00", 1.5],
				20: ["#fa0", 1.2],
				Infinity: ["#000", 1]
			}
		},
		scores: {
			now: [0, 0], //当前
			goal: [0, 0], //目标
			t0: +new Date(),
			k: 0.005
		}, //分数 白:0 黑:1
		lines: [], //分析连线
		linesDisplay: false, //显示连线
		
		//更新时间显示
		updateTime(turn){
			let total, step,
				totalElem, stepElem;
			
			//黑
			total = Math.max(this.time[1].total, 0);
			step = Math.max(this.time[1].step, 0);
			
			totalElem = $("#main > .black > div > .time > .total"),
			stepElem = totalElem.nextAll(".step");
			
			totalElem.html(
				( ~~(total/60) +"").padStart(2,"0") + ":" + ( ~~total%60 +"").padStart(2, "0")
			);
			for (const [i,v] of Object.entries(this.timeWarning.total))
				if (total <= i){
					totalElem.css("color", v[0])
						.css("font-size", v[1] + "em");
					break;
				}
			
			stepElem.html(
				~~step + "s"
			);
			for (const [i,v] of Object.entries(this.timeWarning.step))
				if (step <= i){
					stepElem.css("color", v[0])
						.css("font-size", v[1] + "em");
					break;
				}
			
			if (~~step < 10 && ~~step >= 9)
				this.flash(this.turn, "#f00"); //背景闪烁
			
			//白
			total = Math.max(this.time[0].total, 0);
			step = Math.max(this.time[0].step, 0);
			
			totalElem = $("#main > .white > div > .time > .total"),
			stepElem = totalElem.nextAll(".step");
			
			totalElem.html(
				( ~~(total/60) +"").padStart(2,"0") + ":" + ( ~~total%60 +"").padStart(2, "0")
			);
			for (const [i,v] of Object.entries(this.timeWarning.total))
				if (total <= i){
					totalElem.css("color", v[0])
						.css("font-size", v[1] + "em");
					break;
				}
			
			stepElem.html(
				~~step + "s"
			);
			for (const [i,v] of Object.entries(this.timeWarning.step))
				if (step <= i){
					stepElem.css("color", v[0])
						.css("font-size", v[1] + "em");
					break;
				}
			
			if (~~step < 10 && ~~step >= 9)
				this.flash(this.turn, "#f00"); //背景闪烁
		},
		
		//背景闪烁
		flash(turn, color){
			let elem;
			if (this.turn == 1){ //黑
				elem = $("#main > div.black > i");
			}else{ //白
				elem = $("#main > div.white > i");
			}
			elem.css("background-color", color);
			setTimeout(()=> elem.css("background-color", ""), 150);
			setTimeout(()=> elem.css("background-color", color), 300);
			setTimeout(()=> elem.css("background-color", ""), 450);
		},
		
		//切换下棋方
		set(turn){
			if (this.time[this.turn].total <= 0){ //超出局时
				this.time[this.turn].step += 5;
			}else{
				this.time[this.turn].step = stepTime; //重置步时
				//this.time[this.turn].total += 5; //增加局时
			}
			this.updateTime(this.turn);
			clearInterval( this.ids[this.turn] );
			
			this.turn = turn;
			this.flash(this.turn, "#af8"); //背景闪烁
			this.t0[this.turn] = +new Date();
			this.ids[this.turn] = setInterval(()=>{
				const interval = (new Date() - this.t0[this.turn]) / 1000;
				this.t0[this.turn] = +new Date();
				
				this.time[this.turn].total -= interval;
				this.time[this.turn].step -= interval;
				this.updateTime(this.turn);
				
				if (this.time[this.turn].step <= 0){ //超出步时
					if (game.winner !== false) return; //游戏已结束
					game.over(this.turn!=this.first, this.turn==1?"黑方超时":"白方超时");
				}
				
			}, 200);
		},
		
		//游戏结束
		over(win, text){
			console.log("game over", win, text)
			
			const t = (new Date() - this.startTime) / 1000;
			clearInterval( this.ids[this.turn] );
			$("#toolBtn > .review").removeAttr("disabled"); //解除禁用
			
			if (win === true){
				this.winner = +this.first;
				layer.alert(`<span>${text}</span><br><span>历时</span>${~~(t/60)}<span>分</span>${~~t%60}<span>秒</span><br>${game.steps}<span>步</span><span>（</span>${game.steps/2}<span>回合</span><span>）<span>`,
					{title: "胜利！", icon: 6}
				);
				
				game_record.two.win++;
				game_record.two[N].win++;
				game_record.two[N][+this.first].win++;
				game_record.two[N][+this.first][sizeText].win++;
				saveGameRecord();
				if (gameOverMusic)
					gameOverMusic.stop();
				gameOverMusic = new Player("./music/win.mp3").play();
				
			}else if (win === false){
				this.winner = 1-this.first;
				layer.alert(`<span>${text}</span><br><span>历时</span>${~~(t/60)}<span>分</span>${~~t%60}<span>秒</span><br>${game.steps}<span>步</span><span>（</span>${game.steps/2}<span>回合</span><span>）<span>`,
					{title: "你失败了！", icon: 5}
				);
				
				//战绩
				game_record.two.fail++;
				game_record.two[N].fail++;
				game_record.two[N][+this.first].fail++;
				game_record.two[N][+this.first][sizeText].fail++;
				saveGameRecord();
				if (gameOverMusic)
					gameOverMusic.stop();
				gameOverMusic = new Player("./music/fail.mp3").play();
				
			}else{
				this.winner = null;
				layer.alert(`<span>${text}</span><br><span>历时</span>${~~(t/60)}<span>分</span>${~~t%60}<span>秒</span><br>${game.steps}<span>步</span><span>（</span>${game.steps/2}<span>回合</span><span>）<span>`,
					{title: "游戏结束"}
				);
			}
		},
		
		//更新分数
		updateScores(){
			const t = new Date()-this.scores.t0,
				now = this.scores.now,
				goal = this.scores.goal,
				k = this.scores.k,
				e = $("#tools > .scores");
			this.scores.t0 = +new Date();
			if (t > 300) return; //跳帧
			
			now[0] += (goal[0] - now[0]) *t *k;
			now[1] += (goal[1] - now[1]) *t *k;
			
			const black = now[1] + 0.001,
				white = now[0] + 0.001,
				total = black + white,
				ctx = e[0].getContext("2d"),
				width = e.attr("width"),
				height = e.attr("height");
			
			//清除背景
			ctx.beginPath();
			ctx.fillStyle = "#fff";
			ctx.fillRect(0, 0, width, height);
			
			//背景
			ctx.beginPath();
			ctx.fillStyle = "#000";
			ctx.moveTo(0, 0);
			ctx.lineTo(black / total * width + 5, 0);
			ctx.lineTo(black / total * width - 5, height);
			ctx.lineTo(0, height);
			ctx.fill();
			
			ctx.beginPath();
			ctx.fillStyle = "#fff";
			ctx.moveTo(width, 0);
			ctx.lineTo(black / total * width + 5, 0);
			ctx.lineTo(black / total * width - 5, height);
			ctx.lineTo(width, height);
			ctx.fill();
			
			//文字
			ctx.beginPath();
			ctx.fillStyle = "#fff"; //白字
			ctx.textAlign = "left";
			ctx.textBaseline = "top"; //基线：左上
			ctx.font = height*5/6 + "px xs"; // 5/6vh
			ctx.shadowOffsetX = 1;
			ctx.shadowOffsetY = 1;
			ctx.shadowColor = "#000";
			ctx.shadowBlur = 3;
			ctx.fillText(Math.round(black-0.001, 2), height/6, height/12); //margin: 0.2vh ;0.1vh
			
			ctx.beginPath();
			ctx.fillStyle = "#000"; //黑字
			ctx.textAlign = "right";
			ctx.textBaseline = "top"; //基线：右上
			ctx.font = height*5/6 + "px xs"; // 5/6vh
			ctx.shadowOffsetX = 1;
			ctx.shadowOffsetY = 1;
			ctx.shadowColor = "#fff";
			ctx.shadowBlur = 3;
			ctx.fillText(Math.round(white-0.001, 2), width-height/6, height/12); //margin: 0.2vh ;0.1vh
		},
		
		//更新分析画线
		updateLines(display=true){
			if (!display){
				board.updateCallback.analyse = null;
				
			}else{
				board.updateCallback.analyse = ()=>{
					const ctx = board.ctx;
					
					for (const v of this.lines){
						if (v.width < 3) //连子<3 避免过密
							continue;
						ctx.beginPath();
						ctx.strokeStyle = v.pic==2? "#fff": "#000";
						ctx.lineWidth = v.width-3+1;
						ctx.moveTo(...v.start);
						ctx.lineTo(...v.end);
						ctx.stroke();
					}
				};
			}
			board.update();
			
			game.linesDisplay = display;
		}
	},
	worker,
	workerCallback = {  //收到消息回调
		//电脑下
		computer(data){
			const {pos, value} = data;
			if (game.winner !== false) return; //游戏已结束
			if (game.first){ //电脑白棋
				board.remove(-1)
					.add(pos[0], pos[1], place, "#fff", "#000")
					.add(pos[0], pos[1], place, undefined, "#fff", 2, board.picSize+2);
				game.set(1);
			}else{ //电脑黑棋
				board.remove(-1)
					.add(pos[0], pos[1], place, "#000")
					.add(pos[0], pos[1], place, undefined, "#fff", 2, board.picSize+2);
				game.set(0);
			}
		},
		
		//获胜
		won(data){
			const {winner, steps} = data;
			
			if (game.winner !== false) return; //游戏已结束
			game.over(winner-1==game.first, winner==2?"黑方胜":"白方胜");
		},
		
		//获胜画线
		lines(data){
			const {lines} = data;
			
			lines.forEach( v => {
				[v.start[0], v.start[1]] = board.ij2xy(...v.start, place);
				[v.end[0], v.end[1]] = board.ij2xy(...v.end, place);
			});
			
			board.updateCallback.win = ()=>{
				const ctx = board.ctx;
				
				ctx.beginPath();
				ctx.strokeStyle = "#f00";
				ctx.lineWidth = N-3+1;
				for (const v of lines){
					ctx.moveTo(v.start[0], v.start[1]);
					ctx.lineTo(v.end[0], v.end[1]);
					ctx.stroke();
				}
			};
			board.update();
			//worker.terminate(); //关闭线程
		},
		
		//平局
		gameOver(){
			//worker.terminate(); //关闭线程
			
			if (game.winner !== false) return; //游戏已结束
			game.over(null, "平局");
		},
		
		//分析
		scores(data){
			console.table(data.scores);
			
			const {scores, lines} = data;
			lines.forEach( v => {
				[v.start[0], v.start[1]] = board.ij2xy(...v.start, place);
				[v.end[0], v.end[1]] = board.ij2xy(...v.end, place);
			});
			
			game.scores.goal = scores;
			game.lines = lines;
			
			if (board.updateCallback.analyse){
				game.updateLines(true);
			}else{
				game.updateLines(false);
			}
		}
	},
	scores_id = null, //分数更新id
	review_id = null; //复盘id



//初始化
function init(){
	//游戏结束音乐
	if (gameOverMusic)
		gameOverMusic.stop();
	
	//棋盘
	board.updateCallback = ()=>{};
	board.clear()
		.add((cols-1)/2, (rows-1)/2, place, "#000", undefined, undefined, 10) //多加一个以备删除
		.add((cols-1)/2, (rows-1)/2, place, "#000", undefined, undefined, 10);
	
	//游戏
	game.turn = 1;
	game.first = null;
	game.steps = 0;
	game.winner = false;
	clearInterval( game.ids[0] );
	clearInterval( game.ids[1] ); //时间暂停
	game.t0[0] = game.t0[1] = game.ids[0] = game.ids[1] = null;
	game.time[0].total = totalTime;
	game.time[0].step = stepTime;
	game.time[1].total = totalTime;
	game.time[1].step = stepTime;
	game.scores.now[0] = game.scores.now[1] =
		game.scores.goal[0] = game.scores.goal[1] = 0;
	game.scores.t0 = +new Date();
	game.scores.k = 0.005;
	setInterval(()=>game.updateScores(), 16); //更新分数
	game.lines = []; //清空分析画线
	
	//关闭工具
	scores(false);
	analyse(false);
	clearInterval(review_id); //停止复盘
	review_id = null;
	$("#tools > .review > .stop").hide();
	$("#tools > .review > .play").show();
	$("#toolBtn > .review").attr("disabled", "disabled");
	$("#tools > .review").slideUp("fast");
	
	//选择先行
	const finish_choose = ()=>{
		//战绩
		game_record.two.total++;
		game_record.two[N].total++;
		game_record.two[N][+game.first].total++;
		game_record.two[N][+game.first][sizeText].total++;
		saveGameRecord();
		
		if (game.first){ //自己先行
			$("#main > div.self").removeClass("white").addClass("black");
			$("#main > div.opposite").removeClass("black").addClass("white");
			layer.msg("你执黑棋，电脑执白棋");
		}else{ //电脑先行
			$("#main > div.self").removeClass("black").addClass("white");
			$("#main > div.opposite").removeClass("white").addClass("black");			
			layer.msg("你执白棋，电脑执黑棋");
			request({
				type: "computer",
				side: 2 //电脑黑棋
			});
		}
		game.set(1); //黑棋先下
		game.startTime = +new Date(); //开始计时
	};
	layer.confirm("请选择先手/后手", {
		btn: ["随机", "先手", "后手"],
		btn1(){
			game.first = !!~~(Math.random()*2);
			finish_choose(); //选择完毕
		},
		btn2(){
			game.first = true;
			finish_choose(); //选择完毕
		},
		btn3(){
			game.first = false;
			finish_choose(); //选择完毕
		}
	});
	
	//线程
	if (worker)
		worker.terminate(); //关闭线程
	worker = new Worker("./js/worker.js"); //多线程
	worker.postMessage({
		type: "init", //初始化
		cols,
		rows,
		D: 2,
		N,
		mis: mistake
	});
	worker.onmessage = function(e){
		console.log("message: ", e.data.type, e.data)
		const type = e.data.type;
		if (workerCallback[type])
			workerCallback[type](e.data);
	};
	worker.onerror = function(e){
		console.error(`${e.message}\n\tat ${e.filename}:${e.lineno}`)
	};
}
init();



//请求线程
function request(data){
	console.log("post: ", data.type, data)
	worker.postMessage(data);
	return {
		then(func){
			workerCallback[data.type] = func;
		}
	};
}


let waiting = false; //等待中
$("#board").click(function(e){
	if (!worker) return; //未初始化线程
	if (game.first === null) return; //未开始
	if (game.winner !== false) return; //已结束
	if (waiting) return; //上次未响应
	
	playEffect(); //播放音效
	
	const {offsetX: x, offsetY: y} = e;
	const [i, j] = board.xy2ij(x,y, place);
	console.log("click", [x, y], [i, j])
	
	waiting = true; //开始等待
	
	request({
		type: "play", //下棋
		i, j
	}).then((data)=>{
		waiting = false;
		console.log("play callback:", data.action, {i,j})
		switch (data.action){
			case 2: //下黑棋
				board.remove(-1)
					.add(i, j, place, "#000")
					.add(i, j, place, undefined, "#fff", 2, board.picSize+2);
				game.set(0);
				game.steps++;
				if (game.first && !game.winner) //自己先手 且 未结束
					request({
						type: "computer",
						side: 1 //电脑白棋
					});
				
				break;
				
			case 1: //下白棋
				board.remove(-1)
					.add(i, j, place, "#fff", "#000")
					.add(i, j, place, undefined, "#fff", 2, board.picSize+2);
				game.set(1);
				game.steps++;
				if (!game.first && !game.winner) //自己后手 且 未结束
					request({
						type: "computer",
						side: 2 //电脑黑棋
					});
				
				break;
				
			case -2: //添加标记
				board.add(i, j, place, undefined, "#00f", 2);
				break;
				
			case -1: //删除标记
				board.remove(-1);
				break;
		}
	});
});



/* header */
//返回
$("#back").click(function(){
	go("index.html")
});

//重来
$("#restart").click(init);


/* tools */
//评分
function scores(display){
	const btn = $("#toolBtn > .scores"),
		tool = $("#tools > .scores");
	if ( display === true || //欲显示
		(display === undefined && tool.css("display") == "none") //已隐藏
	){
		clearInterval( scores_id ); //停止更新
		scores_id = setInterval(()=>game.updateScores(), 30); //更新分数
		
		btn.attr("disabled", "disabled");
		tool.slideDown("slow", ()=>btn.removeAttr("disabled"));
		
	}else{
		clearInterval( scores_id ); //停止更新
		scores_id = null;
		
		btn.attr("disabled", "disabled");
		tool.slideUp("slow", ()=>btn.removeAttr("disabled"));
	}
}
$("#toolBtn > .scores").click(function(){
	if ( $("#tools > .review").css("display") != "none" ){
		scores(false);
		return layer.msg("复盘时暂不支持棋局评分");
	}
	scores();
});

//分析
function analyse(display){
	if ( display === false || //欲隐藏
		(display === undefined && game.linesDisplay) //已显示
	){
		if (game.linesDisplay) //已显示
			layer.msg("棋局分析已关闭");
		game.updateLines(false);
	}else{
		if (!game.linesDisplay) //已隐藏
			layer.msg("棋局分析已开启");
		game.updateLines(true);
	}
}
$("#toolBtn > .analyse").click(function(){
	if ($("#tools > .review").css("display") != "none"){
		analyse(false);
		return layer.msg("复盘时暂不支持棋局分析");
	}
	analyse();
});

//重放
let reviewRestore; //重置函数
function review(){
	if ( $("#tools > .review").css("display") == "none" ){ //需显示
		$(this).attr("disabled", "disabled");
		$("#tools > .review").slideToggle("slow", ()=>$(this).removeAttr("disabled"));
		
		scores(false); //关闭棋局评分
		analyse(false); //关闭棋局分析
		$("#tools > .review > .stop").hide();
		$("#tools > .review > .play").show(); //暂停
		
		request({
			type: "review" //重放
		}).then((data)=>{
			const {steps, pieces} = data;
			
			const winCallback = board.updateCallback.win;
			board.updateCallback.win = board.updateCallback.analyse = null;
			board.clear(); //清空
			
			let index = -1;
			
			const next = ()=>{
					if (index >= steps.length-1){
						board.updateCallback.win = winCallback;
						board.update();
						return layer.msg("不能再往后了");
					}
					const {i,j,v} = steps[++index];
					board.remove(-1)
						.add(i, j, place, v==2? "#000": "#fff", v==2? "#fff":"#000")
						.add(i, j, place, undefined, "#fff", 2, board.picSize+2);
					return false;
				},
				last = ()=>{
					if (index <= 0)
						return layer.msg("不能再往前了"); 
					const {i,j,v} = steps[--index];
					board.updateCallback.win = null;
					board.remove(-1)
						.remove(-1)
						.add(i, j, place, undefined, "#fff", 2, board.picSize+2);
					return false;
				};
			reviewRestore = ()=>{
				board.clear();
				for (const {i,j,v} of steps)
					board.add(i, j, place, v==2? "#000": "#fff", v==2? "#fff":"#000")
				board.updateCallback.win = winCallback;
				board.update();
			};
			
			next();
			
			$("#tools > .review > .last").click(last);
			$("#tools > .review > .next").click(next);
			
			$("#tools > .review > .play").click(function(){
				$(this).hide();
				$("#tools > .review > .stop").show();
				
				if (review_id)
					clearInterval(review_id);
				
				review_id = setInterval(()=>{
					if (next() === false) return;
					clearInterval(review_id);
					review_id = null;
					$(this).show();
					$("#tools > .review > .stop").hide();
				}, 1000);
			});
			$("#tools > .review > .stop").click(function(){
				clearInterval(review_id);
				review_id = null;
				$(this).hide();
				$("#tools > .review > .play").show();
			});
		});
		
	}else{
		clearInterval(review_id);
		review_id = null;
		reviewRestore(); //重置
		
		$("#tools > .review > .stop").hide();
		$("#tools > .review > .play").show();
		$(this).attr("disabled", "disabled");
		$("#tools > .review").slideToggle("slow", ()=>$(this).removeAttr("disabled"));
	}
}
$("#toolBtn > .review").click(review);


/* 电脑版按键 */
document.onkeydown = function(e){
	console.log(e.key)
	if (e.key == "Escape"){
		go("index.html");
	}else if (e.key == "F2"){ //评分
		if (!$("#toolBtn > .scores").attr("disabled"))
			scores();
	}else if (e.key == "F3"){ //分析
		if (!$("#toolBtn > .analyse").attr("disabled"))
			analyse();
	}else if (e.key == "F4"){ //复盘
		if (!$("#toolBtn > .review").attr("disabled"))
			review();
	}else if (e.key == "F5"){ //再来一局
		init();
	}
	e.preventDefault();
	return false;
};
</script>
		
	</body>
</html>
